package com.kevin.session;

import java.io.InputStreamReader;
import java.util.List;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.lang3.StringUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;

import com.kevin.datasource.DataSourceFactory;
import com.kevin.datasource.Environment;
import com.kevin.exceptions.BaseException;
import com.kevin.exceptions.ExceptionFactory;
import com.kevin.transaction.TransactionFactory;
import com.kevin.transaction.jdbc.JdbcTransactionFactory;

public class XMLConfigBuilder {
	private final InputStreamReader reader;
	protected final Configuration configuration;
	private boolean parsed;
	private String environment;

	public XMLConfigBuilder() {
		this(null);
	}

	public XMLConfigBuilder(InputStreamReader reader) {
		this.reader = reader;
		this.configuration = new Configuration();
	}

	public Configuration parse() {
		if (parsed) {
			throw new BaseException("Each XMLConfigBuilder can only be used once.");
		}
		parsed = true;

		try {
			Document document = new SAXBuilder().build(reader);
			Element rootElement = document.getRootElement();
			if (rootElement.getName().equals("configuration")) {
				parseConfiguration(rootElement);
			} else {
				rootElement = rootElement.getChild("configuration");
				if (rootElement != null) {
					parseConfiguration(rootElement);
				} else {
					new BaseException("Not find element name's configuration");
				}
			}
		} catch (Exception e) {
			throw ExceptionFactory.Exception(e);
		}
		return configuration;
	}

	private void parseConfiguration(Element root) {

		try {
			environmentsElement(root.getChild("environments"));
		} catch (Exception e) {
			throw ExceptionFactory.Exception(e);
		}

	}

	private boolean isSpecifiedEnvironment(String id) {
		if (environment == null) {
			throw new BaseException("No environment specified.");
		} else if (id == null) {
			throw new BaseException("Environment requires an id attribute.");
		} else if (environment.equals(id)) {
			return true;
		}
		return false;
	}

	@SuppressWarnings("unchecked")
	public Properties getChildrenAsProperties(Element element) {
		Properties properties = new Properties();
		List<Element> eleList = element.getChildren();
		for (Element child : eleList) {
			String name = child.getAttributeValue("name");
			String value = child.getAttributeValue("value");
			if (name != null && value != null) {
				properties.setProperty(name, value);
			}
		}
		return properties;
	}

	private TransactionFactory transactionManagerElement(Element context)
			throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		if (context != null) {

			String type = context.getAttributeValue("class");
			Properties props = getChildrenAsProperties(context);
			Class<?> tranClazz = null;

			if (StringUtils.isEmpty(type)) {
				tranClazz = JdbcTransactionFactory.class;
			} else {
				tranClazz = Class.forName(type);
			}

			TransactionFactory factory = (TransactionFactory) tranClazz.newInstance();
			factory.setProperties(props);
			return factory;
		}
		throw new BaseException("Environment declaration requires a TransactionFactory.");
	}

	private DataSourceFactory dataSourceElement(Element context)
			throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		if (context != null) {

			String type = context.getAttributeValue("class");
			Properties props = getChildrenAsProperties(context);
			Class<?> tranClazz = null;

			if (StringUtils.isEmpty(type)) {
				tranClazz = JdbcTransactionFactory.class;
			} else {
				tranClazz = Class.forName(type);
			}
			DataSourceFactory factory = (DataSourceFactory) tranClazz.newInstance();
			factory.setProperties(props);
			return factory;

		}
		throw new BaseException("Environment declaration requires a DataSourceFactory.");
	}

	@SuppressWarnings("unchecked")
	private void environmentsElement(Element context) throws Exception {
		if (context != null) {
			if (environment == null) {
				environment = context.getAttributeValue("default");
			}

			List<Element> exeEleList = context.getChildren();

			for (Element child : exeEleList) {
				String id = child.getAttributeValue("id");
				if (isSpecifiedEnvironment(id)) {
					TransactionFactory txFactory = transactionManagerElement(child.getChild("transactionManager"));
					DataSourceFactory dsFactory = dataSourceElement(child.getChild("dataSource"));
					DataSource dataSource = dsFactory.getDataSource();
					Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory)
							.dataSource(dataSource);
					configuration.setEnvironment(environmentBuilder.build());
				}
			}
		}
	}
}
